package cz.muni.fi.pb138.videoweb2013.backend;

import static cz.muni.fi.pb138.videoweb2013.backend.MediumManagerImpl.log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.BasicConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.exist.xmldb.EXistResource;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;
import org.xmldb.api.*;

/**
 *
 * @author Lenka
 */
public class MediumManagerImpl implements MediumManager{
    
    String URI = "xmldb:exist://localhost:8080/exist/xmlrpc/db/";
    String driver = "org.exist.xmldb.DatabaseImpl";
    String COLLECTION = "VideoWeb";
    String USERNAME = "admin";
    String PASSWORD = "admin";    
    
    final static Logger log = LoggerFactory.getLogger(MediumManagerImpl.class);    
    Collection col;
    
    public MediumManagerImpl()
    {
        //readConfigurationFile("databaseConfiguration.properties");
        col = this.initialise();
        BasicConfigurator.configure();
        prepareDatabase();
    }  
    public MediumManagerImpl(String confFile)
    {
        readConfigurationFile(confFile);
        col = this.initialise();
        BasicConfigurator.configure();
        prepareDatabase();
    }
            
    
    public List<Category> getCategories()
    {
        String queryId = "for $x in //category return string($x/@id)";
        String queryName = "for $x in //category return string($x/@name)";
        List<String> IDs = new ArrayList<String>();
        List<String> names = new ArrayList<String>();
        List<Category> categories = new ArrayList<Category>();        
        Resource res = null;
        
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(queryId);
            ResourceIterator i = result.getIterator();            
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String catID = res.getContent().toString();
                    IDs.add(catID);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
            result = xpqs.query(queryName);
            i = result.getIterator();            
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String catName = res.getContent().toString();
                    names.add(catName);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
            for(int j=0;j<IDs.size();j++)
            {
                List<String> header = new ArrayList<String>();
                String queryHeader = "for $x in //category[@id = \"" + IDs.get(j) + "\"]/header/column/@name return string($x)";
                result = xpqs.query(queryHeader);
                i = result.getIterator();            
                while(i.hasMoreResources()) {
                    try {
                        res = i.nextResource();
                        String h = res.getContent().toString();
                        header.add(h);
                    } finally {
                        try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                    }
                }
                categories.add(new Category(IDs.get(j),names.get(j),header));            
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }       
        return categories;
    }
    
    
    public List<Medium> getMediumByString(String s) 
    {
        String query = "for $x in //medium[record/text() = \"" + s + "\"]/@id return string($x)";
        Resource res = null;
        List<Medium> media = new ArrayList<Medium>();
        int id = 0;
        Medium m;
               
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(query);
            ResourceIterator i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String x = res.getContent().toString();
                    id = Integer.parseInt(x);
                    m = getMediumById(id);
                    media.add(m);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());            
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }        
        return media;
    }
    
    public int addRecord(Medium m)
    {
        String xml = prepare(m);
        int id = getNewID();
        String query = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">\n"+
                "<xupdate:insert-after select=\"/myVideoWeb/category[@id='"+m.getCathegoryId()+"']/header[1]\" > "+  
                "<xupdate:element name=\"medium\"> " + 
                "<xupdate:attribute name=\"id\">" + id + "</xupdate:attribute>" + 
                xml +
                "</xupdate:element>" +
                "</xupdate:insert-after>" +
                "</xupdate:modifications>"; 
        
        try{
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("base.xml", query);
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }       
        return id;
        
    }
    
    public void deleteRecord(Medium m)
    {
        String query = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">\n"+
               "<xupdate:remove select=\"//medium[@id='"+m.getId()+"']\" /> "+  
                "</xupdate:modifications>"; 
     try{
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("base.xml", query);
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }               
    }
    
    public Medium getMediumById(int id) 
    {
        String categoryIdQuery = "string(/myVideoWeb/category[medium/@id=\"" + id + "\"]/@id)"; 
        String categoryNameQuery = "string(/myVideoWeb/category[medium/@id=\"" + id + "\"]/@name)";   
        String mediumQuery = "/myVideoWeb/category/medium[@id=\"" + id + "\"]/record";
        Map <String,String> content = new HashMap<String,String>();
        Resource res = null;        
        Medium m = null;;
        String catId = "";
        String catName = "";
        List<String> header= null;
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(categoryIdQuery);
            ResourceIterator i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            else {
                try {
                    res = i.nextResource();
                    String x = res.getContent().toString();
                    catId = x;
                    header = existCathegory(catId);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
            result = xpqs.query(categoryNameQuery);
            i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            else {
                try {
                    res = i.nextResource();
                    String x  = res.getContent().toString();
                    catName = x;
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
            result = xpqs.query(mediumQuery);
            i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String x  = res.getContent().toString();
                    String[] xx = x.split("\"");
                    String column = (xx[1]);
                    String value = "";
                    xx = x.split(">");
                    if (xx.length>1)
                    {
                        String[] xxx = xx[1].split("<");
                        value = xxx[0];                        
                    }
                    content.put(column, value);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());            
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }        
        m = new Medium(id,content,catName,catId,header);        
        return m;
    }
    
    public void updateRecord(Medium m)
    {
       this.deleteRecord(m);
       String xml = prepare(m);
        int id = m.getId();
        String query = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">\n"+
               "<xupdate:insert-after select=\"/myVideoWeb/category[@id='"+m.getCathegoryId()+"']/header[1]\" > "+  
                "<xupdate:element name=\"medium\"> " + 
                "<xupdate:attribute name=\"id\">" + id + "</xupdate:attribute>" + 
                xml +
                "</xupdate:element>" +
                "</xupdate:insert-after>" +
                "</xupdate:modifications>"; 
        
        try{
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("base.xml", query);
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }       
        
       
    }
    
    public List<Medium> getAll()
    {
        List<String> IDs = new ArrayList<String>();
        String query = "for $x in //medium/@id return string($x)";
        Resource res = null;
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(query);
            ResourceIterator i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String x = res.getContent().toString();
                    IDs.add(x);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
            
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
        List<Medium> allMedia = new ArrayList<Medium>();
        for(String sId : IDs)
        {
            int id = Integer.parseInt(sId);
            Medium m;
            m = this.getMediumById(id);
            allMedia.add(m);
        }        
        return allMedia;
    }
    
    public String getXML()
    {
        String xml = "";
        XMLResource res = null;
        try {  
            res = (XMLResource)col.getResource("base.xml");
            
            if(res == null) {
                System.out.println("document not found!");
            } else {
                xml = res.getContent().toString();
            }
        }catch(XMLDBException xmle){
            log.error(xmle.toString());
        }                   
        finally {            
            if(res != null) {
                try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
            
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }
        return xml;
    }
    
    public void clearDatabase()
    {
        String query = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">\n"+
               "<xupdate:remove select=\"//category\" /> "+  
                "</xupdate:modifications>"; 
     try{
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("base.xml", query);
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }     
    }
    
    /**
     * Function transform received medium in a format witch can be added to database
     * In addition it calls the function addNewCathegory(Medium) if needed.
     * @param Medium
     * @return String transformated medium  
     */
    private String prepare(Medium m)
    {
        List<String> cHeader = existCathegory(m.getCathegoryId());
        List<String> mHeader = m.getHeader();
        Map<String,String> mContent = m.getItem();
        String xml = "";
        
        if (cHeader!=null)
        {
            for(String column : cHeader)
            {
                if (mHeader.contains(column) && mContent.containsKey(column))
                {
                    xml += "<record column = \"" + column + "\" >";
                    xml += mContent.get(column);
                    xml += "</record>\n";
                }
                else
                {
                    log.error("Medium is in inconsistent state. This should never happened");
                }
            }
        }
        if(cHeader==null)
        {
            addNewCathegory(m);
            cHeader = existCathegory(m.getCathegoryId());
            for(String column : cHeader)
            {
                if (mHeader.contains(column) && mContent.containsKey(column))
                {
                    xml += "<record column = \"" + column + "\" >";
                    xml += mContent.get(column);
                    xml += "</record>\n";
                }
                else
                {
                    log.error("Medium is in inconsistent state. This should never happened");
                }
            }
        }
        
        return xml;
    }
    
    /**
     * Create new category in database,
     * all needed information it takes frmo parameter Medium
     * @param Medium
     */
    private void addNewCathegory(Medium m)
    {
        String xml = "";
        xml+= "<header>\n";
        for(String column : m.getHeader())
        {
            xml+= "<column name=\"" + column + "\"/>\n";
        }
        xml+="</header>\n";
        String query = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">"+
                "<xupdate:append select=\"/myVideoWeb\" child = \"last()\"> "+ 
                "<xupdate:element name=\"category\"> " + 
                "<xupdate:attribute name=\"id\">" + m.getCathegoryId() + "</xupdate:attribute>" + 
                "<xupdate:attribute name=\"name\">" + m.getCathegoryName() + "</xupdate:attribute>" +
                xml +
                "</xupdate:element>" +
                "</xupdate:append>" +
                "</xupdate:modifications>";        
        
        try{
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("base.xml", query);
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }        
    }
    
    /**
     * Using category id function is lloking for category in database     *
     * @param String category id
     * @return List<String> columns of category
     *          (if category don't exist listis empty)  
     */
    private List<String> existCathegory(String c)
    {
        String query = "/myVideoWeb/category[@id=\""+c+"\"]/header/column";
        Resource res = null;
        List<String> columns = new ArrayList<String>();
               
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(query);
            ResourceIterator i = result.getIterator();
            if (!i.hasMoreResources()) return null;
            while(i.hasMoreResources()) {
                try {
                    res = i.nextResource();
                    String x = res.getContent().toString();
                    String[] xx = x.split("\"");
                    columns.add(xx[1]);
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());       
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }        
        return columns;
    }
    
    /**
     * Establish the connection with database and select working collection      
     */
    private Collection initialise()
    {
        Class cl = null;
        Collection col = null;
        try
        {
            cl = Class.forName(driver);
            Database database = (Database) cl.newInstance();
            database.setProperty("create-database", "true");
            DatabaseManager.registerDatabase(database);
            col = DatabaseManager.getCollection(URI + COLLECTION,USERNAME,PASSWORD);
        }
        catch(Exception e)
        {
            log.error("problem with database connection." + e);
            return null;
        } 
        return col;
            
    }
    
    /**
     * Detect wheather the collection exist and if not create one inside the working collection
     * @param String name of new collection  
     */
    private void createCollection(String name)
    {
        try{
            col = DatabaseManager.getCollection(URI + name);
            if (col == null)
            {
                Collection parent = DatabaseManager.getCollection(URI,USERNAME,PASSWORD);
                CollectionManagementService mgt = (CollectionManagementService) parent.getService("CollectionManagementService", "1.0");
                Collection x = mgt.createCollection(name);
                x.close();
                parent.close();
            }
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }   
    }
    
    /**
     * Detect wheather the resource exist and if not
     * create one inside the working collection and fill it with the content of xml file
     * @param String path and name to xml file
     * @param String name of created resource
     */
    private void createResource(String filename, String name)
    {
        try
        {
            XMLResource res = (XMLResource)col.getResource(name);            
            if(res == null) 
            {
                res = (XMLResource)col.createResource(name, "XMLResource");
            File f = new File(filename);
            if(!f.canRead()) {
               log.error("cannot read file " + filename);
                return;
            }
            res.setContent(f);
            col.storeResource(res);
            }            
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }   
    }
    
    /**
     * Aggregate some function needed to start working with database
     */
    private void prepareDatabase()
    {
        createCollection(COLLECTION);
        col = initialise();
        createResource(MediumManagerImpl.class.getClassLoader().getResource("").getPath()+"../../../../"+"id.xml","nextID.xml");
        createResource(MediumManagerImpl.class.getClassLoader().getResource("").getPath()+"../../../../"+"video.xml","base.xml");
    }
        
    /**
     * Load informations from file witch are needed to prepare database
     * @param String path and name of configuration file
     */
    private void readConfigurationFile(String name)
    {   
        BufferedReader br = null;
        try
        {
            File f = new File(name);
            br  = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
            String line = br.readLine();
            int control = 0;
            for(int i = 0;i<5;i++)
            {
                String[] x = line.split("=");
                String var = x[0].replaceAll("\\s","");
                if (var.equals("URI"))
                {
                    URI = x[1].replaceAll("\\s","");
                    control++;                    
                }
                if (var.equals("driver"))
                {
                    driver = x[1].replaceAll("\\s","");
                    control++;                    
                }
                if (var.equals("COLLECTION"))
                {
                    COLLECTION = x[1].replaceAll("\\s","");
                    control++;                    
                }
                if (var.equals("USERNAME"))
                {
                    USERNAME = x[1].replaceAll("\\s","");
                    control++;                    
                }
                if (var.equals("PASSWORD"))
                {
                    PASSWORD = x[1].replaceAll("\\s","");
                    control++;                    
                }
                if (control<i+1)
                {
                    log.error("Bad format of configuration file: " + name);
                }
                line = br.readLine();
                
            }
        
            
            
        }catch(FileNotFoundException fnfe)
        {
            log.error("configuration file " + name + "doesn't exist");
        }catch(IOException ioe)
        {
            log.error("Problem during IO operation");
        }
        finally
        {
            try
            {if (br != null)br.close();}
            catch(IOException ioe)
            {
                log.error("It is impossible to close bufferReader");
            }
        }
        
    }
        
    /**
     * Select from database new unique id of medium
     * @return int generated id
     */
    private int getNewID()
    {
        String querySelect = "/nextID/text()";        
        Resource res = null;
        int int_id = 0;
        
        try{
            XPathQueryService xpqs = (XPathQueryService)col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
            ResourceSet result = xpqs.query(querySelect);
            ResourceIterator i = result.getIterator();            
            if(i.hasMoreResources()) {
                try {
                    res = i.nextResource();                    
                } finally {
                    try { ((EXistResource)res).freeResources(); } catch(XMLDBException xe) {xe.printStackTrace();}
                }
            }
            String string_id = res.getContent().toString().replaceAll("\\s","");
            int_id = Integer.parseInt(string_id);
            String queryUpdate = "<xupdate:modifications version=\"1.0\" xmlns:xupdate=\"http://www.xmldb.org/xupdate\">"+
                            "<xupdate:update select=\"/nextID\"> \n" +
                            (int_id +1) + " </xupdate:update> " + "</xupdate:modifications>";
            
            XUpdateQueryService xuqs = (XUpdateQueryService)col.getService("XUpdateQueryService", "1.0");
            xuqs.updateResource("nextID.xml", queryUpdate );
        }
        catch(XMLDBException xmle){
            log.error(xmle.toString());
        }finally {
            if(col != null) {
                try { col.close(); } catch(XMLDBException xe) {xe.printStackTrace();}
            }
        }        
        return int_id;
    }
   
}
